#include "DemoProtobuf.h"
#include "TestProtobufData.pb.h"

using namespace std;

void simpleDemo()
{
    // 序列化
    TestProtobufData outData;
    outData.set_id(1);
    outData.set_lid(10l);
    outData.set_name("summer");
    outData.add_tags();
    outData.set_tags(0, "标签1");
    outData.add_tags();
    (*outData.mutable_tags(1)) = "标签2";
    (*outData.add_tags()) = "标签3";
    outData.add_tags("标签4");
    outData.mutable_subdata()->set_id(100000);
    outData.mutable_subdata()->set_content("这是一个内嵌子对象");
    outData.set_color(Color::Red);
    outData.mutable_subfiledata()->set_id(100001);
    outData.mutable_subfiledata()->set_content("这是一个引入其他protobuf的子对象");
    outData.mutable_subpackagedata()->set_id(100001);
    outData.mutable_subpackagedata()->set_content("这是一个引入其他 protobuf 文件且归属于某个包下的子对象");
    string outStr;
    outData.SerializeToString(&outStr);
    cout << "out::" << outStr << endl;

    // 反序列化
    TestProtobufData inData;
    inData.ParseFromString(outStr);
    cout << "in::"
         << "id:" << inData.id() << ",lid:" << inData.lid() << ",name:" << inData.name() << endl;
    cout << "in::"
         << "tags:";
    for (int i = 0; i < inData.tags_size(); ++i)
    {
        cout << inData.tags(i) << ",";
    }
    cout << endl;
    if (inData.has_subdata())
    {
        cout << "in::"
             << "subdata:{id:" << inData.subdata().id() << ", content:" << inData.subdata().content() << "}" << endl;
    }
    cout << "in::"
         << "color:" << inData.color() << endl;
    if (inData.has_subfiledata())
    {
        cout << "in::"
             << "subfiledata:{id:" << inData.subfiledata().id() << ", content:" << inData.subfiledata().content() << "}" << endl;
    }
    if (inData.has_subpackagedata())
    {
        ProtoData::SubPackageProtobufData subpackagedata = inData.subpackagedata();
        cout << "in::"
             << "subpackagedata:{id:" << subpackagedata.id() << ", content:" << subpackagedata.content() << "}" << endl;
    }
}

void runDemoOfProtobuf()
{
    // https://zhuanlan.zhihu.com/p/645555627?utm_id=0
    // 根据proto文件生成源码命令(需要配置环境变量否则protoc需要指定全路径)：
    // -I 指定proto文件所在目录，即在哪个目录下搜索proto文件（注意 -I 后空格，在某些资料里说可以写=连接但实际不行）。
    // --cpp_out 指定源文件输出目录，protoc 指令会根据proto文件名来生成对应源码文件名。
    // 最后跟上需要生成的文件名以空格隔开。
    // protoc -I ..\LearnSerialization\ --cpp_out=..\LearnSerialization\ SubPackageProtobufData.proto SubFileProtobufData.proto TestProtobufData.proto

    // Protobuf 概述
    // protobuf 也叫 protocol buffer 是 google 的一种数据交换的格式，它独立于语言，独立于平台。
    // google 提供了多种语言的实现：java、c#、c++、go 和 python 等，每一种实现都包含了相应语言的编译器以及库文件。
    //
    // 由于它是一种二进制的格式，比使用 xml 、json进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。
    // 作为一种效率和兼容性都很优秀的二进制数据传输格式，可以用于诸如网络传输、配置文件、数据存储等诸多领域。
    //
    // 源码安装
    // https://github.com/protocolbuffers/protobuf/releases
    //
    // 温馨提示：以下按照步骤只适用于 3.21.12 及以下版本，高于该版本的安装方式就不一样了。
    // linxun:
    /* shell：
        # 以 protobuf 3.21.12 为例
        # 自行下载源码包, 解压缩
        $ tar zxvf protobuf-cpp-3.21.12.tar.gz
        # 进入到解压目录
        $ cd protobuf-3.21.12/
        # 构建并安装
        $ ./configure         # 检查安装环境, 生成 makefile
        $ make                # 编译
        $ sudo make install   # 安装
    */
    // 在 linux 下，执行完上述操作，还会可能有一些问题：
    //  1. protoc: error while loading shared libraries: libprotoc.so.32: cannot open shared object file: No such file or directory
    //     * 通过输出的信息可以看到找不到动态库，此时可以通过 find 进行搜索
    //          $ sudo find /usr/local/ -name libprotoc.so
    //          /usr/local/lib/libprotoc.so
    //       使用 find 命令进行搜索的时候，如果从 /usr/local/ 目录中搜索不到需要的文件信息，可以从根目录 / 开始搜索。
    //     * 从搜索的结果可以看到这个动态库位于 /usr/local/lib 目录，接下来需要需要将这目录添加到 /etc/ld.so.conf 这个配置文件中：
    //          $ sudo vim /etc/ld.so.conf
    //       将搜索到的 /usr/local/lib 字串，添加到配置文件
    //     * 最后，需要更新配置，在终端执行如下命令：
    //          $ sudo ldconfig
    // 实际上可以使用 cmake 来编译 protobuf，获得静态库或共享库，直接包含在项目中，这样可以免去系统依赖，方便统一打包。
    // windows:
    /*
        1. 解压源码
        2-1. 使用 Cmake 导出 VS 项目通过 VS 编译生成 dll和lib文件
        2-2. 或者使用 Cmake 通过 MinGw 直接编译项目得到 .a 文件
        3. 将库文件引入导项目使用，注意：VS 生成的 DLL 是动态库，lib 有两种：作为静态库存在或作为DLL的符号声明引入库存在引入lib通过lib链接dll。
        4. 整理头文件，引入项目中，可以在项目中引入 src 文件夹，但是里面会包含源码（包含源码也不影响工程），
           如果有洁癖可以指引入.h文件（手动剔除，或在linux系统下安装后找linux下的头文件集合）
    */
    // 如何使用
    // 在学习protbuf之前，我们需要搞清楚使用protbuf进行数据的序列化主要有哪几个步骤：
    //  1. 确定数据格式，数据可简单可复杂，比如：
    /*
            // 要序列化的数据
            // 第一种: 单一数据类型
            int number;

            // 第二种: 复合数据类型
            struct Person
            {
                int id;
                string name;
                string sex;
                int age;
            };
    */
    //  2. 创建一个新的文件, 文件名随意指定, 文件后缀为 .proto
    //  3. 根据protobuf的语法, 编辑.proto文件
    //  4. 使用 protoc 命令将 .proto 文件转化为相应的 C++ 文件
    //     * 源文件: xxx.pb.cc –> xxx对应的名字和 .proto文件名相同
    //     * 头文件: xxx.pb.h –> xxx对应的名字和 .proto文件名相同
    //  5. 需要将生成的c++文件添加到项目中, 通过文件中提供的类 API 实现数据的序列化/反序列化
    //
    // protobuf 中的数据类型 和 C++ 数据类型对照表:
    /*
        rotobuf类型              C++类型                        备注
        double                   double                         64位浮点数
        float                    float                          32位浮点数
        int32                    int                            32位整数
        int64                    long                           64位整数
        uint32                   unsigned int                   32位无符号整数
        uint64                   unsigned long                  64位无符号整数
        sint32                   signed int                     32位整数，处理负数效率比int32更高
        sint64                   signed long                    64位整数，处理负数效率比int64更高
        fixed32                  unsigned int(32位)             总是4个字节。如果数值总是比总是比228大的话，这个类型会比uint32高效。
        fixed64                  unsigned long(64位)            总是8个字节。如果数值总是比总是比256大的话，这个类型会比uint64高效。
        sfixed32                 int (32位)                     总是4个字节
        sfixed64                 long (64位)                    总是8个字节
        bool                     bool                           布尔类型
        string                   string                         一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本
        bytes                    string                         处理多字节的语言字符、如中文, 建议protobuf中字符型类型使用 bytes
        enum                     enum                           枚举
        message                  object of class                自定义的消息类型

    */
    // Protobuf 语法
    // 基本使用:
    //  1. 创建一个以 .proto 为后缀的文本文件。
    //     TestProtobufData.proto
    //  2. 在文件的第一行需要指定 Protobuf 的版本号，有两个版本Protobuf 2 和 Protobuf 3，此处我们使用的是版本3：
    //     syntax="proto3";
    //  3. 接着定义消息体（消息就是 proto 里的自定义数据结构，任何数据哪怕只有一个 int 类型变量也需要使用 message 来组织描述:
    /*
            message 名字	
            {
                // 类中的成员, 格式
                数据类型 成员名字 = 1;
                数据类型 成员名字 = 2;
                数据类型 成员名字 = 3;
                ......     
                ......
            }
    */
    //   * message后面的名字就是生成的类的名字，自己指定一个合适的名字即可
    //   * 等号后面的编号要从1开始，每个成员都有一个唯一的编号，不能重复，一般连续编号即可。
    // 基于上面的基本用法可以参考： TestProtobufData.proto 文件
    //
    // 通过命令转化生成源码文件： $ protoc -I path .proto文件 --cpp_out=输出路径(存储生成的c++文件) 
    //  * 在 protoc 命令中，-I 参数后面可以跟随一个或多个路径，用于告诉编译器在哪些路径下查找导入的文件或依赖的文件，使用绝对路径或相对路径都是没问题的。
    //  * 如果有多个路径，可以使用多个 -I 参数或在一个参数中使用冒号（:）分隔不同的路径。如果只有一个路径-I 参数可以省略。
    //    例如，protoc -I path1 -I path2 或 protoc -I path1:path2 都表示告诉编译器在 path1 和 path2 路径下查找导入的文件或依赖的文件。
    // 我们可以在.proto文件所在目录执行protoc命令，并生成到当前目录：
    //   $ protoc ./Person.proto --cpp_out=.
    // 
    // 使用 repeated 限定修饰符来定义数组（结合 TestProtobufData.proto 文件来观看）
    //   repeated bytes tags = 4;
    //
    // 使用 enum 来定义枚举，注意枚举定义的第一个枚举类型选项值必须等于 0 （结合 TestProtobufData.proto 文件来观看）
    //   enum Color
    //
    // 在 proto 文件中导入其他 proto 文件（结合 TestProtobufData.proto 文件来观看）
    //   import "要使用的proto文件的名字";  
    // 可以是使用相对或绝对路径，默认在当前路径下搜索
    //
    // 使用包(package)来管理命名空间：在C++中表现为命名空间，在其他语言中比如 java 表现为不同的包名（结合 SubPackageProtobufData.proto 文件来观看）
    //   package ProtoData;
    // 在申明处使用上面语句来申明，那么在引用处则需要带上全包名来使用： ProtoData.SubPackageProtobufData （结合 TestProtobufData.proto 文件来观看）
    // 
    simpleDemo();
}